#include <regex.h>
#include "re.h"
#include "str.h"
#include "log.h"


namespace evm { namespace re {

static string errstr(int errcode, const regex_t& re) {
    size_t errbuf_size = regerror(errcode, &re, 0, 0);
    char* errbuf = new char[errbuf_size];
    regerror(errcode, &re, errbuf, errbuf_size);
    string errstr(errbuf);
    delete [] errbuf;
    return errstr;
}

RegexObject::RegexObject(const string& pattern, int flags) {
    int cflags = REG_EXTENDED;
    eflags_ |= REG_NOTBOL | REG_NOTEOL;
    if (flags & IGNORECASE)
        cflags |= REG_ICASE;
    if (flags & MUILTILINE)
        eflags_ = 0;  // default eflags
    if (flags & DOTALL)
        cflags |= REG_NEWLINE;

    //log_info("pattern: ({})", pattern);

    // here nmatch is not computed accurately, but may be redundant
    nmatch_ = str::count(pattern, "(") + 1;

    int errcode = regcomp(&re_, pattern.data(), cflags);
    if (errcode != 0) {
        throw invalid_argument("regcomp error: " + errstr(errcode, re_));
    }
}

RegexObject::~RegexObject() {
    regfree(&re_);
}

MatchObject RegexObject::search(const string& s, size_t start, size_t end) const {
    if (start > s.size())
        start = s.size();
    if (end > s.size())
        end = s.size();
    if (start > end)
        throw invalid_argument("re search error: start "
                               + to_string(start) + " > end " + to_string(end));

    string slice_str = str::slice(s, start, end);

    regmatch_t* pmatch = new regmatch_t[nmatch_];

    MatchObject m(s);

    //log_info("nmatch: {}, slice_str: ({})", nmatch_, slice_str);

    int errcode = regexec(&re_, slice_str.data(), nmatch_, pmatch, eflags_);
    if (errcode == REG_NOMATCH) {
        log_debug("regexec fail: {}", errstr(errcode, re_));
    }
    else {
        for (size_t i = 0; i < nmatch_; ++i) {
            auto sub_start = pmatch[i].rm_so;
            auto sub_end = pmatch[i].rm_eo;
            //log_info("start, end: ({}, {})", sub_start, sub_end);
            if (sub_start != NO_MATCH)
                sub_start += start;
            if (sub_end != NO_MATCH)
                sub_end += start;
            m.matches_.emplace_back(sub_start, sub_end);
        }
        m.matched_ = true;
    }

    delete [] pmatch;
    return m;
}

string MatchObject::group(size_t index) {
    if (index >= matches_.size())
        throw runtime_error("invalid group index");
    auto offset = matches_[index];
    return string(str_.cbegin() + offset.first, str_.cbegin() + offset.second);
}

MatchObject RegexObject::match(const string& str, bool whole) const {
    auto m = search(str, 0, str.size());
    /*log_info("m.group(): {}, size: {}", m.group(), m.group().size());*/
    m.matched_ = m && ((whole && str == m.group()) || str::startswith(str, m.group()));
    return m;
}

vector<MatchObject> RegexObject::search_all(const string &str, size_t start, size_t end) const {
    vector<MatchObject> matches;

    size_t every_start = start;
    while (true) {
        auto match = search(str, every_start, end);
        if (!match)
            return matches;

        matches.push_back(std::move(match));

        // update next start index with this end index
        every_start = static_cast<size_t>(match.matches_[0].second);
    }
}

}}
